/*
Copyright 2010,2011 Matt Van Der Westhuizen

This file is part of CGLL.

CGLL is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

CGLL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with CGLL.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.chaoticengine.cgll.collision;

import java.util.ArrayList;
import java.util.List;
import org.chaoticengine.cgll.entity.Entity;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Shape;

/**
 * This broad phase collision detector partitions the screen into squares of
 * equal size and uses the squares as buckets, placing each entity into all
 * buckets it intersects with. The narrow phase then simply compares all
 * entities in each bucket, which in theory should provide a big saving on the
 * number of comparisons that need to be performed.
 *
 * @author Matt v.d. Westhuizen
 */
public class PartitionBroadPhaseCollisionDetector implements IBroadPhaseCollisionDetector{
    private INarrowPhaseCollisionDetector npCd = null;
    private int scrWidth = 0;
    private int scrHeight = 0;
    private int bucketSize = 0;
    private int bucketRows = 0;
    private int bucketCols = 0;
    private List<List<Entity>> buckets = null;
    
    public PartitionBroadPhaseCollisionDetector(INarrowPhaseCollisionDetector npCd,
            int screenWidthPx, int screenHeightPx, int bucketSizePx) {
        this.npCd = npCd;
        this.scrWidth = screenWidthPx;
        this.scrHeight = screenHeightPx;
        this.bucketSize = bucketSizePx;
        this.bucketCols = scrWidth / bucketSize;
        this.bucketRows = scrHeight / bucketSize;

        // Create the buckets
        int numBuckets = (scrWidth / bucketSize) * (scrHeight / bucketSize);
        this.buckets = new ArrayList<List<Entity>>(numBuckets);

        for (int i = 0; i < numBuckets; i++) {
            this.buckets.add(new ArrayList<Entity>());
        }
    }

    public void addEntities(List<Entity> entities) {
        for (int i = 0; i < entities.size(); i++) {
            addEntity(entities.get(i));
        }
    }

    public void addEntity(Entity entity) {
        Shape bounds = entity.getBounds();

        int col = (int) bounds.getX() / bucketSize;
        int row = (int) bounds.getY() / bucketSize;
        int maxCol = col + (int) bounds.getWidth() / bucketSize;
        int maxRow = row + (int) bounds.getHeight() / bucketSize;

        for (; row < maxRow; row++) {
            for (; col < maxCol; col++) {
                List<Entity> bucket = getBucket(col, row);

                if (bucket != null) {
                    bucket.add(entity);
                }
            }
        }
    }

    public void doHitDetection() {
        throw new UnsupportedOperationException("Not supported yet.");

        // TODO: Update bucket contents after entity movement

        // TODO: Perform narrow phase hit detection for each non-empty bucket
    }

    public INarrowPhaseCollisionDetector getNarrowPhaseHitDetector() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void removeEntity(Entity e) {
        
    }

    public void setNarrowPhaseHitDetector(INarrowPhaseCollisionDetector npHd) {
        this.npCd = npHd;
    }

    /**
     * Returns the bucket for the specified position, or null if it is outside
     * the screen (hits outside the screen are ignored).
     * 
     * @param col The column.
     * @param row The row.
     * @return The bucket at the defined position.
     */
    private List<Entity> getBucket(int col, int row) {
        int index = row * bucketCols + col;

        if ((index < 0) || (index >= buckets.size())) {
            return(null);
        } else {
            return(buckets.get(index));
        }
    }
}
